
import java.io.InputStream;
import java.net.URL;
import java.text.MessageFormat;
import java.util.Vector;

import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTError;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.ImageLoader;
import org.eclipse.swt.graphics.ImageLoaderEvent;
import org.eclipse.swt.graphics.ImageLoaderListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.printing.PrintDialog;
import org.eclipse.swt.printing.Printer;
import org.eclipse.swt.printing.PrinterData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Dialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Sash;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

public class ImageAnalyzer {
	Display display;

	Shell shell;

	Canvas imageCanvas, paletteCanvas;

	Label typeLabel, sizeLabel, depthLabel, transparentPixelLabel,
			timeToLoadLabel, screenSizeLabel, backgroundPixelLabel,
			locationLabel, disposalMethodLabel, delayTimeLabel,
			repeatCountLabel, paletteLabel, dataLabel, statusLabel;

	Combo backgroundCombo, scaleXCombo, scaleYCombo, alphaCombo;

	Button incrementalCheck, transparentCheck, maskCheck, backgroundCheck;

	Button previousButton, nextButton, animateButton;

	StyledText dataText;

	Sash sash;

	Color whiteColor, blackColor, redColor, greenColor, blueColor,
			canvasBackground;

	Font fixedWidthFont;

	Cursor crossCursor;

	GC imageCanvasGC;

	int paletteWidth = 140; // recalculated and used as a width hint

	int ix = 0, iy = 0, py = 0; // used to scroll the image and palette

	float xscale = 1, yscale = 1; // used to scale the image

	int alpha = 255; // used to modify the alpha value of the image

	boolean incremental = false; // used to incrementally display an image

	boolean transparent = true; // used to display an image with transparency

	boolean showMask = false; // used to display an icon mask or transparent
	// image mask

	boolean showBackground = false; // used to display the background of an
	// animated image

	boolean animate = false; // used to animate a multi-image file

	Thread animateThread; // draws animated images

	Thread incrementalThread; // draws incremental images

	String lastPath; // used to seed the file dialog

	String currentName; // the current image file or URL name

	String fileName; // the current image file

	ImageLoader loader; // the loader for the current image file

	ImageData[] imageDataArray; // all image data read from the current file

	int imageDataIndex; // the index of the current image data

	ImageData imageData; // the currently-displayed image data

	Image image; // the currently-displayed image

	Vector incrementalEvents; // incremental image events

	long loadTime = 0; // the time it took to load the current image

	static final int INDEX_DIGITS = 4;

	static final int ALPHA_CONSTANT = 0;

	static final int ALPHA_X = 1;

	static final int ALPHA_Y = 2;

	class TextPrompter extends Dialog {
		String message = "";

		String result = null;

		Shell dialog;

		Text text;

		public TextPrompter(Shell parent, int style) {
			super(parent, style);
		}

		public TextPrompter(Shell parent) {
			this(parent, SWT.APPLICATION_MODAL);
		}

		public String getMessage() {
			return message;
		}

		public void setMessage(String string) {
			message = string;
		}

		public String open() {
			dialog = new Shell(getParent(), getStyle());
			dialog.setText(getText());
			dialog.setLayout(new GridLayout());
			Label label = new Label(dialog, SWT.NULL);
			label.setText(message);
			label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
			text = new Text(dialog, SWT.SINGLE | SWT.BORDER);
			GridData data = new GridData(GridData.FILL_HORIZONTAL);
			data.widthHint = 300;
			text.setLayoutData(data);
			Composite buttons = new Composite(dialog, SWT.NONE);
			GridLayout grid = new GridLayout();
			grid.numColumns = 2;
			buttons.setLayout(grid);
			buttons.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END));
			Button ok = new Button(buttons, SWT.PUSH);
			ok.setText("OK");
			data = new GridData();
			data.widthHint = 75;
			ok.setLayoutData(data);
			ok.addSelectionListener(new SelectionAdapter() {
				public void widgetSelected(SelectionEvent e) {
					result = text.getText();
					dialog.dispose();
				}
			});
			Button cancel = new Button(buttons, SWT.PUSH);
			cancel.setText("Cancel");
			data = new GridData();
			data.widthHint = 75;
			cancel.setLayoutData(data);
			cancel.addSelectionListener(new SelectionAdapter() {
				public void widgetSelected(SelectionEvent e) {
					dialog.dispose();
				}
			});
			dialog.setDefaultButton(ok);
			dialog.pack();
			dialog.open();
			while (!dialog.isDisposed()) {
				if (!display.readAndDispatch())
					display.sleep();
			}
			return result;
		}
	}

	public static void main(String[] args) {
		Display display = new Display();
		ImageAnalyzer imageAnalyzer = new ImageAnalyzer();
		Shell shell = imageAnalyzer.open(display);

		while (!shell.isDisposed())
			if (!display.readAndDispatch())
				display.sleep();
		display.dispose();
	}

	public Shell open(Display dpy) {
		// Create a window and set its title.
		this.display = dpy;
		shell = new Shell(display);
		shell.setText("Image_analyzer");

		// Hook resize and dispose listeners.
		shell.addControlListener(new ControlAdapter() {
			public void controlResized(ControlEvent event) {
				resizeShell(event);
			}
		});
		shell.addShellListener(new ShellAdapter() {
			public void shellClosed(ShellEvent e) {
				animate = false; // stop any animation in progress
				if (animateThread != null) {
					// wait for the thread to die before disposing the shell.
					while (animateThread.isAlive()) {
						if (!display.readAndDispatch())
							display.sleep();
					}
				}
				e.doit = true;
			}
		});
		shell.addDisposeListener(new DisposeListener() {
			public void widgetDisposed(DisposeEvent e) {
				// Clean up.
				if (image != null)
					image.dispose();
				whiteColor.dispose();
				blackColor.dispose();
				redColor.dispose();
				greenColor.dispose();
				blueColor.dispose();
				fixedWidthFont.dispose();
				crossCursor.dispose();
			}
		});

		// Create colors and fonts.
		whiteColor = new Color(display, 255, 255, 255);
		blackColor = new Color(display, 0, 0, 0);
		redColor = new Color(display, 255, 0, 0);
		greenColor = new Color(display, 0, 255, 0);
		blueColor = new Color(display, 0, 0, 255);
		fixedWidthFont = new Font(display, "courier", 10, 0);
		crossCursor = new Cursor(display, SWT.CURSOR_CROSS);

		// Add a menu bar and widgets.
		createMenuBar();
		createWidgets();
		shell.pack();

		// Create a GC for drawing, and hook the listener to dispose it.
		imageCanvasGC = new GC(imageCanvas);
		imageCanvas.addDisposeListener(new DisposeListener() {
			public void widgetDisposed(DisposeEvent e) {
				imageCanvasGC.dispose();
			}
		});

		// Open the window
		shell.open();
		return shell;
	}

	void createWidgets() {
		// Add the widgets to the shell in a grid layout.
		GridLayout layout = new GridLayout();
		layout.marginHeight = 0;
		layout.numColumns = 2;
		shell.setLayout(layout);

		// Separate the menu bar from the rest of the widgets.
		Label separator = new Label(shell, SWT.SEPARATOR | SWT.HORIZONTAL);
		GridData gridData = new GridData();
		gridData.horizontalSpan = 2;
		gridData.horizontalAlignment = GridData.FILL;
		separator.setLayoutData(gridData);

		// Add a composite to contain some control widgets across the top.
		Composite controls = new Composite(shell, SWT.NULL);
		RowLayout rowLayout = new RowLayout();
		rowLayout.marginTop = 0;
		rowLayout.marginBottom = 5;
		rowLayout.spacing = 8;
		controls.setLayout(rowLayout);
		gridData = new GridData();
		gridData.horizontalSpan = 2;
		controls.setLayoutData(gridData);

		// Combo to change the background.
		Group group = new Group(controls, SWT.NULL);
		group.setLayout(new RowLayout());
		group.setText("Background");
		backgroundCombo = new Combo(group, SWT.DROP_DOWN | SWT.READ_ONLY);
		backgroundCombo.setItems(new String[] { "None", "White", "Black",
				"Red", "Green", "Blue" });
		backgroundCombo.select(backgroundCombo.indexOf("White"));
		backgroundCombo.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				changeBackground();
			}
		});

		// Combo to change the x scale.
		String[] values = { "0.1", "0.2", "0.3", "0.4", "0.5", "0.6", "0.7",
				"0.8", "0.9", "1", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6",
				"1.7", "1.8", "1.9", "2", "3", "4", "5", "6", "7", "8", "9",
				"10", };
		group = new Group(controls, SWT.NULL);
		group.setLayout(new RowLayout());
		group.setText("X_scale");
		scaleXCombo = new Combo(group, SWT.DROP_DOWN);
		for (int i = 0; i < values.length; i++) {
			scaleXCombo.add(values[i]);
		}
		scaleXCombo.select(scaleXCombo.indexOf("1"));
		scaleXCombo.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				scaleX();
			}
		});

		// Combo to change the y scale.
		group = new Group(controls, SWT.NULL);
		group.setLayout(new RowLayout());
		group.setText("Y_scale");
		scaleYCombo = new Combo(group, SWT.DROP_DOWN);
		for (int i = 0; i < values.length; i++) {
			scaleYCombo.add(values[i]);
		}
		scaleYCombo.select(scaleYCombo.indexOf("1"));
		scaleYCombo.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				scaleY();
			}
		});

		// Combo to change the alpha value.
		group = new Group(controls, SWT.NULL);
		group.setLayout(new RowLayout());
		group.setText("Alpha_K");
		alphaCombo = new Combo(group, SWT.DROP_DOWN | SWT.READ_ONLY);
		for (int i = 0; i <= 255; i += 5) {
			alphaCombo.add(String.valueOf(i));
		}
		alphaCombo.select(alphaCombo.indexOf("255"));
		alphaCombo.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				alpha();
			}
		});

		// Check box to request incremental display.
		group = new Group(controls, SWT.NULL);
		group.setLayout(new RowLayout());
		group.setText("Display");
		incrementalCheck = new Button(group, SWT.CHECK);
		incrementalCheck.setText("Incremental");
		incrementalCheck.setSelection(incremental);
		incrementalCheck.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				incremental = ((Button) event.widget).getSelection();
			}
		});

		// Check box to request transparent display.
		transparentCheck = new Button(group, SWT.CHECK);
		transparentCheck.setText("Transparent");
		transparentCheck.setSelection(transparent);
		transparentCheck.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				transparent = ((Button) event.widget).getSelection();
				if (image != null) {
					imageCanvas.redraw();
				}
			}
		});

		// Check box to request mask display.
		maskCheck = new Button(group, SWT.CHECK);
		maskCheck.setText("Mask");
		maskCheck.setSelection(showMask);
		maskCheck.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				showMask = ((Button) event.widget).getSelection();
				if (image != null) {
					imageCanvas.redraw();
				}
			}
		});

		// Check box to request background display.
		backgroundCheck = new Button(group, SWT.CHECK);
		backgroundCheck.setText("Background");
		backgroundCheck.setSelection(showBackground);
		backgroundCheck.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				showBackground = ((Button) event.widget).getSelection();
			}
		});

		// Group the animation buttons.
		group = new Group(controls, SWT.NULL);
		group.setLayout(new RowLayout());
		group.setText("Animation");

		// Push button to display the previous image in a multi-image file.
		previousButton = new Button(group, SWT.PUSH);
		previousButton.setText("Previous");
		previousButton.setEnabled(false);
		previousButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				previous();
			}
		});

		// Push button to display the next image in a multi-image file.
		nextButton = new Button(group, SWT.PUSH);
		nextButton.setText("Next");
		nextButton.setEnabled(false);
		nextButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				next();
			}
		});

		// Push button to toggle animation of a multi-image file.
		animateButton = new Button(group, SWT.PUSH);
		animateButton.setText("Animate");
		animateButton.setEnabled(false);
		animateButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				animate();
			}
		});

		// Label to show the image file type.
		typeLabel = new Label(shell, SWT.NULL);
		typeLabel.setText("Type_initial");
		typeLabel.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));

		// Canvas to show the image.
		imageCanvas = new Canvas(shell, SWT.V_SCROLL | SWT.H_SCROLL
				| SWT.NO_REDRAW_RESIZE);
		imageCanvas.setBackground(whiteColor);
		imageCanvas.setCursor(crossCursor);
		gridData = new GridData();
		gridData.verticalSpan = 15;
		gridData.horizontalAlignment = GridData.FILL;
		gridData.verticalAlignment = GridData.FILL;
		gridData.grabExcessHorizontalSpace = true;
		gridData.grabExcessVerticalSpace = true;
		imageCanvas.setLayoutData(gridData);
		imageCanvas.addPaintListener(new PaintListener() {
			public void paintControl(PaintEvent event) {
				if (image != null)
					paintImage(event);
			}
		});
		imageCanvas.addMouseMoveListener(new MouseMoveListener() {
			public void mouseMove(MouseEvent event) {
				if (image != null) {
					showColorAt(event.x, event.y);
				}
			}
		});

		// Set up the image canvas scroll bars.
		ScrollBar horizontal = imageCanvas.getHorizontalBar();
		horizontal.setVisible(true);
		horizontal.setMinimum(0);
		horizontal.setEnabled(false);
		horizontal.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				scrollHorizontally((ScrollBar) event.widget);
			}
		});
		ScrollBar vertical = imageCanvas.getVerticalBar();
		vertical.setVisible(true);
		vertical.setMinimum(0);
		vertical.setEnabled(false);
		vertical.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				scrollVertically((ScrollBar) event.widget);
			}
		});

		// Label to show the image size.
		sizeLabel = new Label(shell, SWT.NULL);
		sizeLabel.setText("Size_initial");
		sizeLabel.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));

		// Label to show the image depth.
		depthLabel = new Label(shell, SWT.NULL);
		depthLabel.setText("Depth_initial");
		depthLabel.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));

		// Label to show the transparent pixel.
		transparentPixelLabel = new Label(shell, SWT.NULL);
		transparentPixelLabel.setText("Transparent_pixel_initial");
		transparentPixelLabel.setLayoutData(new GridData(
				GridData.HORIZONTAL_ALIGN_FILL));

		// Label to show the time to load.
		timeToLoadLabel = new Label(shell, SWT.NULL);
		timeToLoadLabel.setText("Time_to_load_initial");
		timeToLoadLabel.setLayoutData(new GridData(
				GridData.HORIZONTAL_ALIGN_FILL));

		// Separate the animation fields from the rest of the fields.
		separator = new Label(shell, SWT.SEPARATOR | SWT.HORIZONTAL);
		separator.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));

		// Label to show the logical screen size for animation.
		screenSizeLabel = new Label(shell, SWT.NULL);
		screenSizeLabel.setText("Animation_size_initial");
		screenSizeLabel.setLayoutData(new GridData(
				GridData.HORIZONTAL_ALIGN_FILL));

		// Label to show the background pixel.
		backgroundPixelLabel = new Label(shell, SWT.NULL);
		backgroundPixelLabel.setText("Background_pixel_initial");
		backgroundPixelLabel.setLayoutData(new GridData(
				GridData.HORIZONTAL_ALIGN_FILL));

		// Label to show the image location (x, y).
		locationLabel = new Label(shell, SWT.NULL);
		locationLabel.setText("Image_location_initial");
		locationLabel
				.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));

		// Label to show the image disposal method.
		disposalMethodLabel = new Label(shell, SWT.NULL);
		disposalMethodLabel.setText("Disposal_initial");
		disposalMethodLabel.setLayoutData(new GridData(
				GridData.HORIZONTAL_ALIGN_FILL));

		// Label to show the image delay time.
		delayTimeLabel = new Label(shell, SWT.NULL);
		delayTimeLabel.setText("Delay_initial");
		delayTimeLabel.setLayoutData(new GridData(
				GridData.HORIZONTAL_ALIGN_FILL));

		// Label to show the background pixel.
		repeatCountLabel = new Label(shell, SWT.NULL);
		repeatCountLabel.setText("Repeats_initial");
		repeatCountLabel.setLayoutData(new GridData(
				GridData.HORIZONTAL_ALIGN_FILL));

		// Separate the animation fields from the palette.
		separator = new Label(shell, SWT.SEPARATOR | SWT.HORIZONTAL);
		separator.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));

		// Label to show if the image has a direct or indexed palette.
		paletteLabel = new Label(shell, SWT.NULL);
		paletteLabel.setText("Palette_initial");
		paletteLabel
				.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));

		// Canvas to show the image's palette.
		paletteCanvas = new Canvas(shell, SWT.BORDER | SWT.V_SCROLL
				| SWT.NO_REDRAW_RESIZE);
		paletteCanvas.setFont(fixedWidthFont);
		paletteCanvas.getVerticalBar().setVisible(true);
		gridData = new GridData();
		gridData.horizontalAlignment = GridData.FILL;
		gridData.verticalAlignment = GridData.FILL;
		GC gc = new GC(paletteLabel);
		paletteWidth = gc.stringExtent("Max_length_string").x;
		gc.dispose();
		gridData.widthHint = paletteWidth;
		gridData.heightHint = 16 * 11; // show at least 16 colors
		paletteCanvas.setLayoutData(gridData);
		paletteCanvas.addPaintListener(new PaintListener() {
			public void paintControl(PaintEvent event) {
				if (image != null)
					paintPalette(event);
			}
		});

		// Set up the palette canvas scroll bar.
		vertical = paletteCanvas.getVerticalBar();
		vertical.setVisible(true);
		vertical.setMinimum(0);
		vertical.setIncrement(10);
		vertical.setEnabled(false);
		vertical.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				scrollPalette((ScrollBar) event.widget);
			}
		});

		// Sash to see more of image or image data.
		sash = new Sash(shell, SWT.HORIZONTAL);
		gridData = new GridData();
		gridData.horizontalSpan = 2;
		gridData.horizontalAlignment = GridData.FILL;
		sash.setLayoutData(gridData);
		sash.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				if (event.detail != SWT.DRAG) {
					((GridData) paletteCanvas.getLayoutData()).heightHint = SWT.DEFAULT;
					Rectangle paletteCanvasBounds = paletteCanvas.getBounds();
					int minY = paletteCanvasBounds.y + 20;
					Rectangle dataLabelBounds = dataLabel.getBounds();
					int maxY = statusLabel.getBounds().y
							- dataLabelBounds.height - 20;
					if (event.y > minY && event.y < maxY) {
						Rectangle oldSash = sash.getBounds();
						sash.setBounds(event.x, event.y, event.width,
								event.height);
						int diff = event.y - oldSash.y;
						Rectangle bounds = imageCanvas.getBounds();
						imageCanvas.setBounds(bounds.x, bounds.y, bounds.width,
								bounds.height + diff);
						bounds = paletteCanvasBounds;
						paletteCanvas.setBounds(bounds.x, bounds.y,
								bounds.width, bounds.height + diff);
						bounds = dataLabelBounds;
						dataLabel.setBounds(bounds.x, bounds.y + diff,
								bounds.width, bounds.height);
						bounds = dataText.getBounds();
						dataText.setBounds(bounds.x, bounds.y + diff,
								bounds.width, bounds.height - diff);
						// shell.layout(true);
					}
				}
			}
		});

		// Label to show data-specific fields.
		dataLabel = new Label(shell, SWT.NULL);
		dataLabel.setText("Pixel_data_initial");
		gridData = new GridData();
		gridData.horizontalSpan = 2;
		gridData.horizontalAlignment = GridData.FILL;
		dataLabel.setLayoutData(gridData);

		// Text to show a dump of the data.
		dataText = new StyledText(shell, SWT.BORDER | SWT.MULTI | SWT.READ_ONLY
				| SWT.V_SCROLL | SWT.H_SCROLL);
		dataText.setBackground(display
				.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
		dataText.setFont(fixedWidthFont);
		gridData = new GridData();
		gridData.horizontalSpan = 2;
		gridData.horizontalAlignment = GridData.FILL;
		gridData.verticalAlignment = GridData.FILL;
		gridData.heightHint = 128;
		gridData.grabExcessVerticalSpace = true;
		dataText.setLayoutData(gridData);
		dataText.addMouseListener(new MouseAdapter() {
			public void mouseDown(MouseEvent event) {
				if (image != null && event.button == 1) {
					showColorForData();
				}
			}
		});
		dataText.addKeyListener(new KeyAdapter() {
			public void keyPressed(KeyEvent event) {
				if (image != null) {
					showColorForData();
				}
			}
		});

		// Label to show status and cursor location in image.
		statusLabel = new Label(shell, SWT.NULL);
		statusLabel.setText("");
		gridData = new GridData();
		gridData.horizontalSpan = 2;
		gridData.horizontalAlignment = GridData.FILL;
		statusLabel.setLayoutData(gridData);
	}

	Menu createMenuBar() {
		// Menu bar.
		Menu menuBar = new Menu(shell, SWT.BAR);
		shell.setMenuBar(menuBar);
		createFileMenu(menuBar);
		createAlphaMenu(menuBar);
		return menuBar;
	}

	void createFileMenu(Menu menuBar) {
		// File menu
		MenuItem item = new MenuItem(menuBar, SWT.CASCADE);
		item.setText("File");
		Menu fileMenu = new Menu(shell, SWT.DROP_DOWN);
		item.setMenu(fileMenu);

		// File -> Open File...
		item = new MenuItem(fileMenu, SWT.PUSH);
		item.setText("OpenFile");
		item.setAccelerator(SWT.MOD1 + 'O');
		item.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				menuOpenFile();
			}
		});

		// File -> Open URL...
		item = new MenuItem(fileMenu, SWT.PUSH);
		item.setText("OpenURL");
		item.setAccelerator(SWT.MOD1 + 'U');
		item.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				menuOpenURL();
			}
		});

		// File -> Reopen
		item = new MenuItem(fileMenu, SWT.PUSH);
		item.setText("Reopen");
		item.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				menuReopen();
			}
		});

		new MenuItem(fileMenu, SWT.SEPARATOR);

		// File -> Save
		item = new MenuItem(fileMenu, SWT.PUSH);
		item.setText("Save");
		item.setAccelerator(SWT.MOD1 + 'S');
		item.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				menuSave();
			}
		});

		// File -> Save As...
		item = new MenuItem(fileMenu, SWT.PUSH);
		item.setText("Save_as");
		item.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				menuSaveAs();
			}
		});

		// File -> Save Mask As...
		item = new MenuItem(fileMenu, SWT.PUSH);
		item.setText("Save_mask_as");
		item.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				menuSaveMaskAs();
			}
		});

		new MenuItem(fileMenu, SWT.SEPARATOR);

		// File -> Print
		item = new MenuItem(fileMenu, SWT.PUSH);
		item.setText("Print");
		item.setAccelerator(SWT.MOD1 + 'P');
		item.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				menuPrint();
			}
		});

		new MenuItem(fileMenu, SWT.SEPARATOR);

		// File -> Exit
		item = new MenuItem(fileMenu, SWT.PUSH);
		item.setText("Exit");
		item.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				shell.close();
			}
		});

	}

	void createAlphaMenu(Menu menuBar) {
		// Alpha menu
		MenuItem item = new MenuItem(menuBar, SWT.CASCADE);
		item.setText("Alpha");
		Menu alphaMenu = new Menu(shell, SWT.DROP_DOWN);
		item.setMenu(alphaMenu);

		// Alpha -> K
		item = new MenuItem(alphaMenu, SWT.PUSH);
		item.setText("K");
		item.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				menuComposeAlpha(ALPHA_CONSTANT);
			}
		});

		// Alpha -> (K + x) % 256
		item = new MenuItem(alphaMenu, SWT.PUSH);
		item.setText("(K + x) % 256");
		item.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				menuComposeAlpha(ALPHA_X);
			}
		});

		// Alpha -> (K + y) % 256
		item = new MenuItem(alphaMenu, SWT.PUSH);
		item.setText("(K + y) % 256");
		item.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				menuComposeAlpha(ALPHA_Y);
			}
		});
	}

	void menuComposeAlpha(int alpha_op) {
		if (image == null)
			return;
		animate = false; // stop any animation in progress
		Cursor waitCursor = new Cursor(display, SWT.CURSOR_WAIT);
		shell.setCursor(waitCursor);
		imageCanvas.setCursor(waitCursor);
		try {
			if (alpha_op == ALPHA_CONSTANT) {
				imageData.alpha = alpha;
			} else {
				imageData.alpha = -1;
				switch (alpha_op) {
				case ALPHA_X:
					for (int y = 0; y < imageData.height; y++) {
						for (int x = 0; x < imageData.width; x++) {
							imageData.setAlpha(x, y, (x + alpha) % 256);
						}
					}
					break;
				case ALPHA_Y:
					for (int y = 0; y < imageData.height; y++) {
						for (int x = 0; x < imageData.width; x++) {
							imageData.setAlpha(x, y, (y + alpha) % 256);
						}
					}
					break;
				default:
					break;
				}
			}
			displayImage(imageData);
		} finally {
			shell.setCursor(null);
			imageCanvas.setCursor(crossCursor);
			waitCursor.dispose();
		}
	}

	void menuOpenFile() {
		animate = false; // stop any animation in progress
		resetScaleCombos();

		// Get the user to choose an image file.
		FileDialog fileChooser = new FileDialog(shell, SWT.OPEN);
		if (lastPath != null)
			fileChooser.setFilterPath(lastPath);
		fileChooser.setFilterExtensions(new String[] {
				"*.bmp; *.gif; *.ico; *.jpg; *.pcx; *.png; *.tif", "*.bmp",
				"*.gif", "*.ico", "*.jpg", "*.pcx", "*.png", "*.tif" });
		fileChooser.setFilterNames(new String[] {
				"All_images" + " (bmp, gif, ico, jpg, pcx, png, tif)",
				"BMP (*.bmp)", "GIF (*.gif)", "ICO (*.ico)", "JPEG (*.jpg)",
				"PCX (*.pcx)", "PNG (*.png)", "TIFF (*.tif)" });
		String filename = fileChooser.open();
		lastPath = fileChooser.getFilterPath();
		if (filename == null)
			return;

		Cursor waitCursor = new Cursor(display, SWT.CURSOR_WAIT);
		shell.setCursor(waitCursor);
		imageCanvas.setCursor(waitCursor);
		try {
			loader = new ImageLoader();
			if (incremental) {
				// Prepare to handle incremental events.
				loader.addImageLoaderListener(new ImageLoaderListener() {
					public void imageDataLoaded(ImageLoaderEvent event) {
						incrementalDataLoaded(event);
					}
				});
				incrementalThreadStart();
			}
			// Read the new image(s) from the chosen file.
			long startTime = System.currentTimeMillis();
			imageDataArray = loader.load(filename);
			loadTime = System.currentTimeMillis() - startTime;
			if (imageDataArray.length > 0) {
				// Cache the filename.
				currentName = filename;
				fileName = filename;

				// If there are multiple images in the file (typically GIF)
				// then enable the Previous, Next and Animate buttons.
				previousButton.setEnabled(imageDataArray.length > 1);
				nextButton.setEnabled(imageDataArray.length > 1);
				animateButton.setEnabled(imageDataArray.length > 1
						&& loader.logicalScreenWidth > 0
						&& loader.logicalScreenHeight > 0);

				// Display the first image in the file.
				imageDataIndex = 0;
				displayImage(imageDataArray[imageDataIndex]);
				resetScrollBars();
			}
		} catch (SWTException e) {
			showErrorDialog("Loading_lc", filename, e);
		} catch (SWTError e) {
			showErrorDialog("Loading_lc", filename, e);
		} finally {
			shell.setCursor(null);
			imageCanvas.setCursor(crossCursor);
			waitCursor.dispose();
		}
	}

	void menuOpenURL() {
		animate = false; // stop any animation in progress
		resetScaleCombos();

		// Get the user to choose an image URL.
		TextPrompter textPrompter = new TextPrompter(shell,
				SWT.APPLICATION_MODAL | SWT.DIALOG_TRIM);
		textPrompter.setText("OpenURLDialog");
		textPrompter.setMessage("EnterURL");
		String urlname = textPrompter.open();
		if (urlname == null)
			return;

		Cursor waitCursor = new Cursor(display, SWT.CURSOR_WAIT);
		shell.setCursor(waitCursor);
		imageCanvas.setCursor(waitCursor);
		try {
			URL url = new URL(urlname);
			InputStream stream = url.openStream();
			loader = new ImageLoader();
			if (incremental) {
				// Prepare to handle incremental events.
				loader.addImageLoaderListener(new ImageLoaderListener() {
					public void imageDataLoaded(ImageLoaderEvent event) {
						incrementalDataLoaded(event);
					}
				});
				incrementalThreadStart();
			}
			// Read the new image(s) from the chosen URL.
			long startTime = System.currentTimeMillis();
			imageDataArray = loader.load(stream);
			stream.close();
			loadTime = System.currentTimeMillis() - startTime;
			if (imageDataArray.length > 0) {
				currentName = urlname;
				fileName = null;

				// If there are multiple images (typically GIF)
				// then enable the Previous, Next and Animate buttons.
				previousButton.setEnabled(imageDataArray.length > 1);
				nextButton.setEnabled(imageDataArray.length > 1);
				animateButton.setEnabled(imageDataArray.length > 1
						&& loader.logicalScreenWidth > 0
						&& loader.logicalScreenHeight > 0);

				// Display the first image.
				imageDataIndex = 0;
				displayImage(imageDataArray[imageDataIndex]);
				resetScrollBars();
			}
		} catch (Exception e) {
			showErrorDialog("Loading", urlname, e);
		} finally {
			shell.setCursor(null);
			imageCanvas.setCursor(crossCursor);
			waitCursor.dispose();
		}
	}

	/*
	 * Called to start a thread that draws incremental images as they are
	 * loaded.
	 */
	void incrementalThreadStart() {
		incrementalEvents = new Vector();
		incrementalThread = new Thread("Incremental") {
			public void run() {
				// Draw the first ImageData increment.
				while (incrementalEvents != null) {
					// Synchronize so we don't try to remove when the vector is
					// null.
					synchronized (ImageAnalyzer.this) {
						if (incrementalEvents != null) {
							if (incrementalEvents.size() > 0) {
								ImageLoaderEvent event = (ImageLoaderEvent) incrementalEvents
										.remove(0);
								if (image != null)
									image.dispose();
								image = new Image(display, event.imageData);
								imageData = event.imageData;
								imageCanvasGC.drawImage(image, 0, 0,
										imageData.width, imageData.height,
										imageData.x, imageData.y,
										imageData.width, imageData.height);
							} else {
								yield();
							}
						}
					}
				}
				display.wake();
			}
		};
		incrementalThread.setDaemon(true);
		incrementalThread.start();
	}

	/*
	 * Called when incremental image data has been loaded, for example, for
	 * interlaced GIF/PNG or progressive JPEG.
	 */
	void incrementalDataLoaded(ImageLoaderEvent event) {
		// Synchronize so that we do not try to add while
		// the incremental drawing thread is removing.
		synchronized (this) {
			incrementalEvents.addElement(event);
		}
	}

	void menuSave() {
		if (image == null)
			return;
		animate = false; // stop any animation in progress

		// If the image file type is unknown, we can't 'Save',
		// so we have to use 'Save As...'.
		if (imageData.type == SWT.IMAGE_UNDEFINED || fileName == null) {
			menuSaveAs();
			return;
		}

		Cursor waitCursor = new Cursor(display, SWT.CURSOR_WAIT);
		shell.setCursor(waitCursor);
		imageCanvas.setCursor(waitCursor);
		try {
			// Save the current image to the current file.
			loader.data = new ImageData[] { imageData };
			loader.save(fileName, imageData.type);

		} catch (SWTException e) {
			showErrorDialog("Saving_lc", fileName, e);
		} catch (SWTError e) {
			showErrorDialog("Saving_lc", fileName, e);
		} finally {
			shell.setCursor(null);
			imageCanvas.setCursor(crossCursor);
			waitCursor.dispose();
		}
	}

	void menuSaveAs() {
		if (image == null)
			return;
		animate = false; // stop any animation in progress

		// Get the user to choose a file name and type to save.
		FileDialog fileChooser = new FileDialog(shell, SWT.SAVE);
		fileChooser.setFilterPath(lastPath);
		if (fileName != null) {
			String name = fileName;
			int nameStart = name.lastIndexOf(java.io.File.separatorChar);
			if (nameStart > -1) {
				name = name.substring(nameStart + 1);
			}
			fileChooser.setFileName(name);
		}
		fileChooser.setFilterExtensions(new String[] { "*.bmp", "*.gif",
				"*.ico", "*.jpg", "*.png" });
		fileChooser.setFilterNames(new String[] { "BMP (*.bmp)", "GIF (*.gif)",
				"ICO (*.ico)", "JPEG (*.jpg)", "PNG (*.png)" });
		String filename = fileChooser.open();
		lastPath = fileChooser.getFilterPath();
		if (filename == null)
			return;

		// Figure out what file type the user wants saved.
		// We need to rely on the file extension because FileDialog
		// does not have API for asking what filter type was selected.
		int filetype = determineFileType(filename);
		if (filetype == SWT.IMAGE_UNDEFINED) {
			MessageBox box = new MessageBox(shell, SWT.ICON_ERROR);
			box.setMessage(createMsg("Unknown_extension", filename
					.substring(filename.lastIndexOf('.') + 1)));
			box.open();
			return;
		}

		if (new java.io.File(filename).exists()) {
			MessageBox box = new MessageBox(shell, SWT.ICON_QUESTION | SWT.OK
					| SWT.CANCEL);
			box.setMessage(createMsg("Overwrite", filename));
			if (box.open() == SWT.CANCEL)
				return;
		}

		Cursor waitCursor = new Cursor(display, SWT.CURSOR_WAIT);
		shell.setCursor(waitCursor);
		imageCanvas.setCursor(waitCursor);
		try {
			// Save the current image to the specified file.
			loader.data = new ImageData[] { imageData };
			loader.save(filename, filetype);

			// Update the shell title and file type label,
			// and use the new file.
			fileName = filename;
			shell.setText(createMsg("Analyzer_on", filename));
			typeLabel
					.setText(createMsg("Type_string", fileTypeString(filetype)));

		} catch (SWTException e) {
			showErrorDialog("Saving_lc", filename, e);
		} catch (SWTError e) {
			showErrorDialog("Saving_lc", filename, e);
		} finally {
			shell.setCursor(null);
			imageCanvas.setCursor(crossCursor);
			waitCursor.dispose();
		}
	}

	void menuSaveMaskAs() {
		if (image == null || !showMask)
			return;
		if (imageData.getTransparencyType() == SWT.TRANSPARENCY_NONE)
			return;
		animate = false; // stop any animation in progress

		// Get the user to choose a file name and type to save.
		FileDialog fileChooser = new FileDialog(shell, SWT.SAVE);
		fileChooser.setFilterPath(lastPath);
		if (fileName != null)
			fileChooser.setFileName(fileName);
		fileChooser.setFilterExtensions(new String[] { "*.bmp", "*.gif",
				"*.ico", "*.jpg", "*.png" });
		fileChooser.setFilterNames(new String[] { "BMP (*.bmp)", "GIF (*.gif)",
				"ICO (*.ico)", "JPEG (*.jpg)", "PNG (*.png)" });
		String filename = fileChooser.open();
		lastPath = fileChooser.getFilterPath();
		if (filename == null)
			return;

		// Figure out what file type the user wants saved.
		// We need to rely on the file extension because FileDialog
		// does not have API for asking what filter type was selected.
		int filetype = determineFileType(filename);
		if (filetype == SWT.IMAGE_UNDEFINED) {
			MessageBox box = new MessageBox(shell, SWT.ICON_ERROR);
			box.setMessage(createMsg("Unknown_extension", filename
					.substring(filename.lastIndexOf('.') + 1)));
			box.open();
			return;
		}

		if (new java.io.File(filename).exists()) {
			MessageBox box = new MessageBox(shell, SWT.ICON_QUESTION | SWT.OK
					| SWT.CANCEL);
			box.setMessage(createMsg("Overwrite", filename));
			if (box.open() == SWT.CANCEL)
				return;
		}

		Cursor waitCursor = new Cursor(display, SWT.CURSOR_WAIT);
		shell.setCursor(waitCursor);
		imageCanvas.setCursor(waitCursor);
		try {
			// Save the mask of the current image to the specified file.
			ImageData maskImageData = imageData.getTransparencyMask();
			loader.data = new ImageData[] { maskImageData };
			loader.save(filename, filetype);

		} catch (SWTException e) {
			showErrorDialog("Saving_lc", filename, e);
		} catch (SWTError e) {
			showErrorDialog("Saving_lc", filename, e);
		} finally {
			shell.setCursor(null);
			imageCanvas.setCursor(crossCursor);
			waitCursor.dispose();
		}
	}

	void menuPrint() {
		if (image == null)
			return;

		try {
			// Ask the user to specify the printer.
			PrintDialog dialog = new PrintDialog(shell, SWT.NULL);
			PrinterData printerData = dialog.open();
			if (printerData == null)
				return;

			Printer printer = new Printer(printerData);
			Point screenDPI = display.getDPI();
			Point printerDPI = printer.getDPI();
			int scaleFactor = printerDPI.x / screenDPI.x;
			Rectangle trim = printer.computeTrim(0, 0, 0, 0);
			if (printer.startJob(currentName)) {
				if (printer.startPage()) {
					GC gc = new GC(printer);
					int transparentPixel = imageData.transparentPixel;
					if (transparentPixel != -1 && !transparent) {
						imageData.transparentPixel = -1;
					}
					Image printerImage = new Image(printer, imageData);
					gc.drawImage(printerImage, 0, 0, imageData.width,
							imageData.height, -trim.x, -trim.y, scaleFactor
									* imageData.width, scaleFactor
									* imageData.height);
					if (transparentPixel != -1 && !transparent) {
						imageData.transparentPixel = transparentPixel;
					}
					printerImage.dispose();
					gc.dispose();
					printer.endPage();
				}
				printer.endJob();
			}
			printer.dispose();
		} catch (SWTError e) {
			MessageBox box = new MessageBox(shell, SWT.ICON_ERROR);
			box.setMessage("Printing_error" + e.getMessage());
			box.open();
		}
	}

	void menuReopen() {
		if (currentName == null)
			return;
		animate = false; // stop any animation in progress
		resetScrollBars();
		resetScaleCombos();
		Cursor waitCursor = new Cursor(display, SWT.CURSOR_WAIT);
		shell.setCursor(waitCursor);
		imageCanvas.setCursor(waitCursor);
		try {
			loader = new ImageLoader();
			long startTime = System.currentTimeMillis();
			ImageData[] newImageData;
			if (fileName == null) {
				URL url = new URL(currentName);
				InputStream stream = url.openStream();
				newImageData = loader.load(stream);
				stream.close();
			} else {
				newImageData = loader.load(fileName);
			}
			loadTime = System.currentTimeMillis() - startTime;
			imageDataIndex = 0;
			displayImage(newImageData[imageDataIndex]);

		} catch (Exception e) {
			showErrorDialog("Reloading", currentName, e);
		} finally {
			shell.setCursor(null);
			imageCanvas.setCursor(crossCursor);
			waitCursor.dispose();
		}
	}

	void changeBackground() {
		String background = backgroundCombo.getText();
		if (background.equals("White")) {
			imageCanvas.setBackground(whiteColor);
		} else if (background.equals("Black")) {
			imageCanvas.setBackground(blackColor);
		} else if (background.equals("Red")) {
			imageCanvas.setBackground(redColor);
		} else if (background.equals("Green")) {
			imageCanvas.setBackground(greenColor);
		} else if (background.equals("Blue")) {
			imageCanvas.setBackground(blueColor);
		} else {
			imageCanvas.setBackground(null);
		}
	}

	/*
	 * Called when the ScaleX combo selection changes.
	 */
	void scaleX() {
		try {
			xscale = Float.parseFloat(scaleXCombo.getText());
		} catch (NumberFormatException e) {
			xscale = 1;
			scaleXCombo.select(scaleXCombo.indexOf("1"));
		}
		if (image != null) {
			resizeScrollBars();
			imageCanvas.redraw();
		}
	}

	/*
	 * Called when the ScaleY combo selection changes.
	 */
	void scaleY() {
		try {
			yscale = Float.parseFloat(scaleYCombo.getText());
		} catch (NumberFormatException e) {
			yscale = 1;
			scaleYCombo.select(scaleYCombo.indexOf("1"));
		}
		if (image != null) {
			resizeScrollBars();
			imageCanvas.redraw();
		}
	}

	/*
	 * Called when the Alpha combo selection changes.
	 */
	void alpha() {
		try {
			alpha = Integer.parseInt(alphaCombo.getText());
		} catch (NumberFormatException e) {
			alphaCombo.select(alphaCombo.indexOf("255"));
			alpha = 255;
		}
	}

	/*
	 * Called when the mouse moves in the image canvas. Show the color of the
	 * image at the point under the mouse.
	 */
	void showColorAt(int mx, int my) {
		int x = mx - imageData.x - ix;
		int y = my - imageData.y - iy;
		showColorForPixel(x, y);
	}

	/*
	 * Called when a mouse down or key press is detected in the data text. Show
	 * the color of the pixel at the caret position in the data text.
	 */
	void showColorForData() {
		int delimiterLength = dataText.getLineDelimiter().length();
		int charactersPerLine = 6 + 3 * imageData.bytesPerLine
				+ delimiterLength;
		int position = dataText.getCaretOffset();
		int y = position / charactersPerLine;
		if ((position - y * charactersPerLine) < 6
				|| ((y + 1) * charactersPerLine - position) <= delimiterLength) {
			statusLabel.setText("");
			return;
		}
		int dataPosition = position - 6 * (y + 1) - delimiterLength * y;
		int byteNumber = dataPosition / 3;
		int where = dataPosition - byteNumber * 3;
		int xByte = byteNumber % imageData.bytesPerLine;
		int x = -1;
		int depth = imageData.depth;
		if (depth == 1) { // 8 pixels per byte (can only show 3 of 8)
			if (where == 0)
				x = xByte * 8;
			if (where == 1)
				x = xByte * 8 + 3;
			if (where == 2)
				x = xByte * 8 + 7;
		}
		if (depth == 2) { // 4 pixels per byte (can only show 3 of 4)
			if (where == 0)
				x = xByte * 4;
			if (where == 1)
				x = xByte * 4 + 1;
			if (where == 2)
				x = xByte * 4 + 3;
		}
		if (depth == 4) { // 2 pixels per byte
			if (where == 0)
				x = xByte * 2;
			if (where == 1)
				x = xByte * 2;
			if (where == 2)
				x = xByte * 2 + 1;
		}
		if (depth == 8) { // 1 byte per pixel
			x = xByte;
		}
		if (depth == 16) { // 2 bytes per pixel
			x = xByte / 2;
		}
		if (depth == 24) { // 3 bytes per pixel
			x = xByte / 3;
		}
		if (depth == 32) { // 4 bytes per pixel
			x = xByte / 4;
		}
		if (x != -1) {
			showColorForPixel(x, y);
		} else {
			statusLabel.setText("");
		}
	}

	/*
	 * Set the status label to show color information for the specified pixel in
	 * the image.
	 */
	void showColorForPixel(int x, int y) {
		if (x >= 0 && x < imageData.width && y >= 0 && y < imageData.height) {
			int pixel = imageData.getPixel(x, y);
			RGB rgb = imageData.palette.getRGB(pixel);

			Object[] args = { new Integer(x), new Integer(y),
					new Integer(pixel), Integer.toHexString(pixel), rgb };
			if (pixel == imageData.transparentPixel) {
				statusLabel.setText(createMsg("Color_at_trans", args));
			} else {
				statusLabel.setText(createMsg("Color_at", args));
			}
		} else {
			statusLabel.setText("");
		}
	}

	/*
	 * Called when the Animate button is pressed.
	 */
	void animate() {
		animate = !animate;
		if (animate && image != null && imageDataArray.length > 1) {
			animateThread = new Thread("Animation") {
				public void run() {
					// Pre-animation widget setup.
					preAnimation();

					// Animate.
					try {
						animateLoop();
					} catch (final SWTException e) {
						display.syncExec(new Runnable() {
							public void run() {
								showErrorDialog(createMsg("Creating_image",
										new Integer(imageDataIndex + 1)),
										currentName, e);
							}
						});
					}

					// Post animation widget reset.
					postAnimation();
				}
			};
			animateThread.setDaemon(true);
			animateThread.start();
		}
	}

	/*
	 * Loop through all of the images in a multi-image file and display them one
	 * after another.
	 */
	void animateLoop() {
		// Create an off-screen image to draw on, and a GC to draw with.
		// Both are disposed after the animation.
		Image offScreenImage = new Image(display, loader.logicalScreenWidth,
				loader.logicalScreenHeight);
		GC offScreenImageGC = new GC(offScreenImage);

		try {
			// Use syncExec to get the background color of the imageCanvas.
			display.syncExec(new Runnable() {
				public void run() {
					canvasBackground = imageCanvas.getBackground();
				}
			});

			// Fill the off-screen image with the background color of the
			// canvas.
			offScreenImageGC.setBackground(canvasBackground);
			offScreenImageGC.fillRectangle(0, 0, loader.logicalScreenWidth,
					loader.logicalScreenHeight);

			// Draw the current image onto the off-screen image.
			offScreenImageGC.drawImage(image, 0, 0, imageData.width,
					imageData.height, imageData.x, imageData.y,
					imageData.width, imageData.height);

			int repeatCount = loader.repeatCount;
			while (animate && (loader.repeatCount == 0 || repeatCount > 0)) {
				if (imageData.disposalMethod == SWT.DM_FILL_BACKGROUND) {
					// Fill with the background color before drawing.
					Color bgColor = null;
					int backgroundPixel = loader.backgroundPixel;
					if (showBackground && backgroundPixel != -1) {
						// Fill with the background color.
						RGB backgroundRGB = imageData.palette
								.getRGB(backgroundPixel);
						bgColor = new Color(null, backgroundRGB);
					}
					try {
						offScreenImageGC
								.setBackground(bgColor != null ? bgColor
										: canvasBackground);
						offScreenImageGC.fillRectangle(imageData.x,
								imageData.y, imageData.width, imageData.height);
					} finally {
						if (bgColor != null)
							bgColor.dispose();
					}
				} else if (imageData.disposalMethod == SWT.DM_FILL_PREVIOUS) {
					// Restore the previous image before drawing.
					offScreenImageGC.drawImage(image, 0, 0, imageData.width,
							imageData.height, imageData.x, imageData.y,
							imageData.width, imageData.height);
				}

				// Get the next image data.
				imageDataIndex = (imageDataIndex + 1) % imageDataArray.length;
				imageData = imageDataArray[imageDataIndex];
				image.dispose();
				image = new Image(display, imageData);

				// Draw the new image data.
				offScreenImageGC.drawImage(image, 0, 0, imageData.width,
						imageData.height, imageData.x, imageData.y,
						imageData.width, imageData.height);

				// Draw the off-screen image to the screen.
				imageCanvasGC.drawImage(offScreenImage, 0, 0);

				// Sleep for the specified delay time before drawing again.
				try {
					Thread.sleep(visibleDelay(imageData.delayTime * 10));
				} catch (InterruptedException e) {
				}

				// If we have just drawn the last image in the set,
				// then decrement the repeat count.
				if (imageDataIndex == imageDataArray.length - 1)
					repeatCount--;
			}
		} finally {
			offScreenImage.dispose();
			offScreenImageGC.dispose();
		}
	}

	/*
	 * Pre animation setup.
	 */
	void preAnimation() {
		display.syncExec(new Runnable() {
			public void run() {
				// Change the label of the Animate button to 'Stop'.
				animateButton.setText("Stop");

				// Disable anything we don't want the user
				// to select during the animation.
				previousButton.setEnabled(false);
				nextButton.setEnabled(false);
				backgroundCombo.setEnabled(false);
				scaleXCombo.setEnabled(false);
				scaleYCombo.setEnabled(false);
				alphaCombo.setEnabled(false);
				incrementalCheck.setEnabled(false);
				transparentCheck.setEnabled(false);
				maskCheck.setEnabled(false);
				// leave backgroundCheck enabled

				// Reset the scale combos and scrollbars.
				resetScaleCombos();
				resetScrollBars();
			}
		});
	}

	/*
	 * Post animation reset.
	 */
	void postAnimation() {
		display.syncExec(new Runnable() {
			public void run() {
				// Enable anything we disabled before the animation.
				previousButton.setEnabled(true);
				nextButton.setEnabled(true);
				backgroundCombo.setEnabled(true);
				scaleXCombo.setEnabled(true);
				scaleYCombo.setEnabled(true);
				alphaCombo.setEnabled(true);
				incrementalCheck.setEnabled(true);
				transparentCheck.setEnabled(true);
				maskCheck.setEnabled(true);

				// Reset the label of the Animate button.
				animateButton.setText("Animate");

				if (animate) {
					// If animate is still true, we finished the
					// full number of repeats. Leave the image as-is.
					animate = false;
				} else {
					// Redisplay the current image and its palette.
					displayImage(imageDataArray[imageDataIndex]);
				}
			}
		});
	}

	/*
	 * Called when the Previous button is pressed. Display the previous image in
	 * a multi-image file.
	 */
	void previous() {
		if (image != null && imageDataArray.length > 1) {
			if (imageDataIndex == 0) {
				imageDataIndex = imageDataArray.length;
			}
			imageDataIndex = imageDataIndex - 1;
			displayImage(imageDataArray[imageDataIndex]);
		}
	}

	/*
	 * Called when the Next button is pressed. Display the next image in a
	 * multi-image file.
	 */
	void next() {
		if (image != null && imageDataArray.length > 1) {
			imageDataIndex = (imageDataIndex + 1) % imageDataArray.length;
			displayImage(imageDataArray[imageDataIndex]);
		}
	}

	void displayImage(ImageData newImageData) {
		if (incremental && incrementalThread != null) {
			// Tell the incremental thread to stop drawing.
			synchronized (this) {
				incrementalEvents = null;
			}

			// Wait until the incremental thread is done.
			while (incrementalThread.isAlive()) {
				if (!display.readAndDispatch())
					display.sleep();
			}
		}

		// Dispose of the old image, if there was one.
		if (image != null)
			image.dispose();

		try {
			// Cache the new image and imageData.
			image = new Image(display, newImageData);
			imageData = newImageData;

		} catch (SWTException e) {
			showErrorDialog("Creating_from" + " ", currentName, e);
			image = null;
			return;
		}

		// Update the widgets with the new image info.
		String string = createMsg("Analyzer_on", currentName);
		shell.setText(string);

		if (imageDataArray.length > 1) {
			string = createMsg("Type_index", new Object[] {
					fileTypeString(imageData.type),
					new Integer(imageDataIndex + 1),
					new Integer(imageDataArray.length) });
		} else {
			string = createMsg("Type_string", fileTypeString(imageData.type));
		}
		typeLabel.setText(string);

		string = createMsg("Size_value", new Object[] {
				new Integer(imageData.width), new Integer(imageData.height) });
		sizeLabel.setText(string);

		string = createMsg("Depth_value", new Integer(imageData.depth));
		depthLabel.setText(string);

		string = createMsg("Transparent_pixel_value",
				pixelInfo(imageData.transparentPixel));
		transparentPixelLabel.setText(string);

		string = createMsg("Time_to_load_value", new Long(loadTime));
		timeToLoadLabel.setText(string);

		string = createMsg("Animation_size_value", new Object[] {
				new Integer(loader.logicalScreenWidth),
				new Integer(loader.logicalScreenHeight) });
		screenSizeLabel.setText(string);

		string = createMsg("Background_pixel_value",
				pixelInfo(loader.backgroundPixel));
		backgroundPixelLabel.setText(string);

		string = createMsg("Image_location_value", new Object[] {
				new Integer(imageData.x), new Integer(imageData.y) });
		locationLabel.setText(string);

		string = createMsg("Disposal_value", new Object[] {
				new Integer(imageData.disposalMethod),
				disposalString(imageData.disposalMethod) });
		disposalMethodLabel.setText(string);

		int delay = imageData.delayTime * 10;
		int delayUsed = visibleDelay(delay);
		if (delay != delayUsed) {
			string = createMsg("Delay_value", new Object[] {
					new Integer(delay), new Integer(delayUsed) });
		} else {
			string = createMsg("Delay_used", new Integer(delay));
		}
		delayTimeLabel.setText(string);

		if (loader.repeatCount == 0) {
			string = createMsg("Repeats_forever", new Integer(
					loader.repeatCount));
		} else {
			string = createMsg("Repeats_value", new Integer(loader.repeatCount));
		}
		repeatCountLabel.setText(string);

		if (imageData.palette.isDirect) {
			string = "Palette_direct";
		} else {
			string = createMsg("Palette_value", new Integer(imageData.palette
					.getRGBs().length));
		}
		paletteLabel.setText(string);

		string = createMsg("Pixel_data_value",
				new Object[] { new Integer(imageData.bytesPerLine),
						new Integer(imageData.scanlinePad),
						depthInfo(imageData.depth) });
		dataLabel.setText(string);

		String data = dataHexDump(dataText.getLineDelimiter());
		dataText.setText(data);

		// bold the first column all the way down
		int index = 0;
		while ((index = data.indexOf(':', index + 1)) != -1)
			dataText.setStyleRange(new StyleRange(index - INDEX_DIGITS,
					INDEX_DIGITS, dataText.getForeground(), dataText
							.getBackground(), SWT.BOLD));

		statusLabel.setText("");

		// Redraw both canvases.
		paletteCanvas.redraw();
		imageCanvas.redraw();
	}

	void paintImage(PaintEvent event) {
		Image paintImage = image;
		int transparentPixel = imageData.transparentPixel;
		if (transparentPixel != -1 && !transparent) {
			imageData.transparentPixel = -1;
			paintImage = new Image(display, imageData);
		}
		int w = Math.round(imageData.width * xscale);
		int h = Math.round(imageData.height * yscale);
		event.gc.drawImage(paintImage, 0, 0, imageData.width, imageData.height,
				ix + imageData.x, iy + imageData.y, w, h);
		if (showMask
				&& (imageData.getTransparencyType() != SWT.TRANSPARENCY_NONE)) {
			ImageData maskImageData = imageData.getTransparencyMask();
			Image maskImage = new Image(display, maskImageData);
			event.gc.drawImage(maskImage, 0, 0, imageData.width,
					imageData.height, w + 10 + ix + imageData.x, iy
							+ imageData.y, w, h);
			maskImage.dispose();
		}
		if (transparentPixel != -1 && !transparent) {
			imageData.transparentPixel = transparentPixel;
			paintImage.dispose();
		}
	}

	void paintPalette(PaintEvent event) {
		GC gc = event.gc;
		gc.fillRectangle(paletteCanvas.getClientArea());
		if (imageData.palette.isDirect) {
			// For a direct palette, display the masks.
			int y = py + 10;
			int xTab = 50;
			gc.drawString("rMsk", 10, y, true);
			gc.drawString(toHex4ByteString(imageData.palette.redMask), xTab, y,
					true);
			gc.drawString("gMsk", 10, y += 12, true);
			gc.drawString(toHex4ByteString(imageData.palette.greenMask), xTab,
					y, true);
			gc.drawString("bMsk", 10, y += 12, true);
			gc.drawString(toHex4ByteString(imageData.palette.blueMask), xTab,
					y, true);
			gc.drawString("rShf", 10, y += 12, true);
			gc.drawString(Integer.toString(imageData.palette.redShift), xTab,
					y, true);
			gc.drawString("gShf", 10, y += 12, true);
			gc.drawString(Integer.toString(imageData.palette.greenShift), xTab,
					y, true);
			gc.drawString("bShf", 10, y += 12, true);
			gc.drawString(Integer.toString(imageData.palette.blueShift), xTab,
					y, true);
		} else {
			// For an indexed palette, display the palette colors and indices.
			RGB[] rgbs = imageData.palette.getRGBs();
			if (rgbs != null) {
				int xTab1 = 40, xTab2 = 100;
				for (int i = 0; i < rgbs.length; i++) {
					int y = (i + 1) * 10 + py;
					gc.drawString(String.valueOf(i), 10, y, true);
					gc.drawString(toHexByteString(rgbs[i].red)
							+ toHexByteString(rgbs[i].green)
							+ toHexByteString(rgbs[i].blue), xTab1, y, true);
					Color color = new Color(display, rgbs[i]);
					gc.setBackground(color);
					gc.fillRectangle(xTab2, y + 2, 10, 10);
					color.dispose();
				}
			}
		}
	}

	void resizeShell(ControlEvent event) {
		if (image == null || shell.isDisposed())
			return;
		resizeScrollBars();
	}

	// Reset the scale combos to 1.
	void resetScaleCombos() {
		xscale = 1;
		yscale = 1;
		scaleXCombo.select(scaleXCombo.indexOf("1"));
		scaleYCombo.select(scaleYCombo.indexOf("1"));
	}

	// Reset the scroll bars to 0.
	void resetScrollBars() {
		if (image == null)
			return;
		ix = 0;
		iy = 0;
		py = 0;
		resizeScrollBars();
		imageCanvas.getHorizontalBar().setSelection(0);
		imageCanvas.getVerticalBar().setSelection(0);
		paletteCanvas.getVerticalBar().setSelection(0);
	}

	void resizeScrollBars() {
		// Set the max and thumb for the image canvas scroll bars.
		ScrollBar horizontal = imageCanvas.getHorizontalBar();
		ScrollBar vertical = imageCanvas.getVerticalBar();
		Rectangle canvasBounds = imageCanvas.getClientArea();
		int width = Math.round(imageData.width * xscale);
		if (width > canvasBounds.width) {
			// The image is wider than the canvas.
			horizontal.setEnabled(true);
			horizontal.setMaximum(width);
			horizontal.setThumb(canvasBounds.width);
			horizontal.setPageIncrement(canvasBounds.width);
		} else {
			// The canvas is wider than the image.
			horizontal.setEnabled(false);
			if (ix != 0) {
				// Make sure the image is completely visible.
				ix = 0;
				imageCanvas.redraw();
			}
		}
		int height = Math.round(imageData.height * yscale);
		if (height > canvasBounds.height) {
			// The image is taller than the canvas.
			vertical.setEnabled(true);
			vertical.setMaximum(height);
			vertical.setThumb(canvasBounds.height);
			vertical.setPageIncrement(canvasBounds.height);
		} else {
			// The canvas is taller than the image.
			vertical.setEnabled(false);
			if (iy != 0) {
				// Make sure the image is completely visible.
				iy = 0;
				imageCanvas.redraw();
			}
		}

		// Set the max and thumb for the palette canvas scroll bar.
		vertical = paletteCanvas.getVerticalBar();
		if (imageData.palette.isDirect) {
			vertical.setEnabled(false);
		} else { // indexed palette
			canvasBounds = paletteCanvas.getClientArea();
			int paletteHeight = imageData.palette.getRGBs().length * 10 + 20; // 10
			// pixels
			// each
			// index
			// + 20
			// for
			// margins.
			vertical.setEnabled(true);
			vertical.setMaximum(paletteHeight);
			vertical.setThumb(canvasBounds.height);
			vertical.setPageIncrement(canvasBounds.height);
		}
	}

	/*
	 * Called when the image canvas' horizontal scrollbar is selected.
	 */
	void scrollHorizontally(ScrollBar scrollBar) {
		if (image == null)
			return;
		Rectangle canvasBounds = imageCanvas.getClientArea();
		int width = Math.round(imageData.width * xscale);
		int height = Math.round(imageData.height * yscale);
		if (width > canvasBounds.width) {
			// Only scroll if the image is bigger than the canvas.
			int x = -scrollBar.getSelection();
			if (x + width < canvasBounds.width) {
				// Don't scroll past the end of the image.
				x = canvasBounds.width - width;
			}
			imageCanvas.scroll(x, iy, ix, iy, width, height, false);
			ix = x;
		}
	}

	/*
	 * Called when the image canvas' vertical scrollbar is selected.
	 */
	void scrollVertically(ScrollBar scrollBar) {
		if (image == null)
			return;
		Rectangle canvasBounds = imageCanvas.getClientArea();
		int width = Math.round(imageData.width * xscale);
		int height = Math.round(imageData.height * yscale);
		if (height > canvasBounds.height) {
			// Only scroll if the image is bigger than the canvas.
			int y = -scrollBar.getSelection();
			if (y + height < canvasBounds.height) {
				// Don't scroll past the end of the image.
				y = canvasBounds.height - height;
			}
			imageCanvas.scroll(ix, y, ix, iy, width, height, false);
			iy = y;
		}
	}

	/*
	 * Called when the palette canvas' vertical scrollbar is selected.
	 */
	void scrollPalette(ScrollBar scrollBar) {
		if (image == null)
			return;
		Rectangle canvasBounds = paletteCanvas.getClientArea();
		int paletteHeight = imageData.palette.getRGBs().length * 10 + 20;
		if (paletteHeight > canvasBounds.height) {
			// Only scroll if the palette is bigger than the canvas.
			int y = -scrollBar.getSelection();
			if (y + paletteHeight < canvasBounds.height) {
				// Don't scroll past the end of the palette.
				y = canvasBounds.height - paletteHeight;
			}
			paletteCanvas.scroll(0, y, 0, py, paletteWidth, paletteHeight,
					false);
			py = y;
		}
	}

	/*
	 * Return a String containing a line-by-line dump of the data in the current
	 * imageData. The lineDelimiter parameter must be a string of length 1 or 2.
	 */
	String dataHexDump(String lineDelimiter) {
		if (image == null)
			return "";
		char[] dump = new char[imageData.height
				* (6 + 3 * imageData.bytesPerLine + lineDelimiter.length())];
		int index = 0;
		for (int i = 0; i < imageData.data.length; i++) {
			if (i % imageData.bytesPerLine == 0) {
				int line = i / imageData.bytesPerLine;
				dump[index++] = Character.forDigit(line / 1000 % 10, 10);
				dump[index++] = Character.forDigit(line / 100 % 10, 10);
				dump[index++] = Character.forDigit(line / 10 % 10, 10);
				dump[index++] = Character.forDigit(line % 10, 10);
				dump[index++] = ':';
				dump[index++] = ' ';
			}
			byte b = imageData.data[i];
			dump[index++] = Character.forDigit((b & 0xF0) >> 4, 16);
			dump[index++] = Character.forDigit(b & 0x0F, 16);
			dump[index++] = ' ';
			if ((i + 1) % imageData.bytesPerLine == 0) {
				dump[index++] = lineDelimiter.charAt(0);
				if (lineDelimiter.length() > 1)
					dump[index++] = lineDelimiter.charAt(1);
			}
		}
		String result = "";
		try {
			result = new String(dump);
		} catch (OutOfMemoryError e) {
			/* Too much data to display in the text widget - truncate at 4M. */
			result = new String(dump, 0, 4 * 1024 * 1024)
					+ "\n ...data dump truncated at 4M...";
		}
		return result;
	}

	/*
	 * Open an error dialog displaying the specified information.
	 */
	void showErrorDialog(String operation, String filename, Throwable e) {
		MessageBox box = new MessageBox(shell, SWT.ICON_ERROR);
		String message = createMsg("Error",
				new String[] { operation, filename });
		String errorMessage = "";
		if (e != null) {
			if (e instanceof SWTException) {
				SWTException swte = (SWTException) e;
				errorMessage = swte.getMessage();
				if (swte.throwable != null) {
					errorMessage += ":\n" + swte.throwable.toString();
				}
			} else if (e instanceof SWTError) {
				SWTError swte = (SWTError) e;
				errorMessage = swte.getMessage();
				if (swte.throwable != null) {
					errorMessage += ":\n" + swte.throwable.toString();
				}
			} else {
				errorMessage = e.toString();
			}
		}
		box.setMessage(message + errorMessage);
		box.open();
	}

	/*
	 * Open a dialog asking the user for more information on the type of BMP
	 * file to save.
	 */
	int showBMPDialog() {
		final int[] bmpType = new int[1];
		bmpType[0] = SWT.IMAGE_BMP;
		SelectionListener radioSelected = new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				Button radio = (Button) event.widget;
				if (radio.getSelection())
					bmpType[0] = ((Integer) radio.getData()).intValue();
			}
		};
		// need to externalize strings
		final Shell dialog = new Shell(shell, SWT.DIALOG_TRIM);

		dialog.setText("Save_as");
		dialog.setLayout(new GridLayout());

		Label label = new Label(dialog, SWT.NONE);
		label.setText("Save_as");

		Button radio = new Button(dialog, SWT.RADIO);
		radio.setText("Save_as_type_no_compress");
		radio.setSelection(true);
		radio.setData(new Integer(SWT.IMAGE_BMP));
		radio.addSelectionListener(radioSelected);

		radio = new Button(dialog, SWT.RADIO);
		radio.setText("Save_as_type_rle_compress");
		radio.setData(new Integer(SWT.IMAGE_BMP_RLE));
		radio.addSelectionListener(radioSelected);

		radio = new Button(dialog, SWT.RADIO);
		radio.setText("Save_as_type_os2");
		radio.setData(new Integer(SWT.IMAGE_OS2_BMP));
		radio.addSelectionListener(radioSelected);

		label = new Label(dialog, SWT.SEPARATOR | SWT.HORIZONTAL);
		label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

		Button ok = new Button(dialog, SWT.PUSH);
		ok.setText("OK");
		GridData data = new GridData();
		data.horizontalAlignment = SWT.CENTER;
		data.widthHint = 75;
		ok.setLayoutData(data);
		ok.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				dialog.close();
			}
		});

		dialog.pack();
		dialog.open();
		while (!dialog.isDisposed()) {
			if (!display.readAndDispatch())
				display.sleep();
		}
		return bmpType[0];
	}

	/*
	 * Return a String describing how to analyze the bytes in the hex dump.
	 */
	static String depthInfo(int depth) {
		Object[] args = { new Integer(depth), "" };
		switch (depth) {
		case 1:
			args[1] = createMsg("Multi_pixels", new Object[] { new Integer(8),
					" [01234567]" });
			break;
		case 2:
			args[1] = createMsg("Multi_pixels", new Object[] { new Integer(4),
					"[00112233]" });
			break;
		case 4:
			args[1] = createMsg("Multi_pixels", new Object[] { new Integer(2),
					"[00001111]" });
			break;
		case 8:
			args[1] = "One_byte";
			break;
		case 16:
			args[1] = createMsg("Multi_bytes", new Integer(2));
			break;
		case 24:
			args[1] = createMsg("Multi_bytes", new Integer(3));
			break;
		case 32:
			args[1] = createMsg("Multi_bytes", new Integer(4));
			break;
		default:
			args[1] = "Unsupported_lc";
		}
		return createMsg("Depth_info", args);
	}

	/*
	 * Return the specified number of milliseconds. If the specified number of
	 * milliseconds is too small to see a visual change, then return a higher
	 * number.
	 */
	static int visibleDelay(int ms) {
		if (ms < 20)
			return ms + 30;
		if (ms < 30)
			return ms + 10;
		return ms;
	}

	/*
	 * Return the specified byte value as a hex string, preserving leading 0's.
	 */
	static String toHexByteString(int i) {
		if (i <= 0x0f)
			return "0" + Integer.toHexString(i);
		return Integer.toHexString(i & 0xff);
	}

	/*
	 * Return the specified 4-byte value as a hex string, preserving leading
	 * 0's. (a bit 'brute force'... should probably use a loop...)
	 */
	static String toHex4ByteString(int i) {
		String hex = Integer.toHexString(i);
		if (hex.length() == 1)
			return "0000000" + hex;
		if (hex.length() == 2)
			return "000000" + hex;
		if (hex.length() == 3)
			return "00000" + hex;
		if (hex.length() == 4)
			return "0000" + hex;
		if (hex.length() == 5)
			return "000" + hex;
		if (hex.length() == 6)
			return "00" + hex;
		if (hex.length() == 7)
			return "0" + hex;
		return hex;
	}

	/*
	 * Return a String describing the specified transparent or background pixel.
	 */
	static String pixelInfo(int pixel) {
		if (pixel == -1)
			return pixel + " (" + "None_lc" + ")";
		else
			return pixel + " (0x" + Integer.toHexString(pixel) + ")";
	}

	/*
	 * Return a String describing the specified disposal method.
	 */
	static String disposalString(int disposalMethod) {
		switch (disposalMethod) {
		case SWT.DM_FILL_NONE:
			return "None_lc";
		case SWT.DM_FILL_BACKGROUND:
			return "Background_lc";
		case SWT.DM_FILL_PREVIOUS:
			return "Previous_lc";
		}
		return "Unspecified_lc";
	}

	/*
	 * Return a String describing the specified image file type.
	 */
	String fileTypeString(int filetype) {
		if (filetype == SWT.IMAGE_BMP)
			return "BMP";
		if (filetype == SWT.IMAGE_BMP_RLE)
			return "RLE" + imageData.depth + " BMP";
		if (filetype == SWT.IMAGE_OS2_BMP)
			return "OS/2 BMP";
		if (filetype == SWT.IMAGE_GIF)
			return "GIF";
		if (filetype == SWT.IMAGE_ICO)
			return "ICO";
		if (filetype == SWT.IMAGE_JPEG)
			return "JPEG";
		if (filetype == SWT.IMAGE_PNG)
			return "PNG";
		return "Unknown_ac";
	}

	/*
	 * Return the specified file's image type, based on its extension. Note that
	 * this is not a very robust way to determine image type, and it is only to
	 * be used in the absence of any better method.
	 */
	int determineFileType(String filename) {
		String ext = filename.substring(filename.lastIndexOf('.') + 1);
		if (ext.equalsIgnoreCase("bmp")) {
			return showBMPDialog();
		}
		if (ext.equalsIgnoreCase("gif"))
			return SWT.IMAGE_GIF;
		if (ext.equalsIgnoreCase("ico"))
			return SWT.IMAGE_ICO;
		if (ext.equalsIgnoreCase("jpg") || ext.equalsIgnoreCase("jpeg"))
			return SWT.IMAGE_JPEG;
		if (ext.equalsIgnoreCase("png"))
			return SWT.IMAGE_PNG;
		return SWT.IMAGE_UNDEFINED;
	}

	static String createMsg(String msg, Object[] args) {
		MessageFormat formatter = new MessageFormat(msg);
		return formatter.format(args);
	}

	static String createMsg(String msg, Object arg) {
		MessageFormat formatter = new MessageFormat(msg);
		return formatter.format(new Object[] { arg });
	}
}
